package org.zoomdy.gdbx.os;

import java.io.IOException;
import java.util.HashMap;

import org.zoomdy.gdbx.Debugger;
import org.zoomdy.gdbx.OSNode;
import org.zoomdy.gdbx.arch.Register;
import org.zoomdy.gdbx.arch.TargetRegisters;
import org.zoomdy.gdbx.arch.TargetRegistersCortexM;

public class FreeRTOS extends RTOS
{
    long addrCurrentTCB;
    long addrReadyTasksLists;
    int sizeReadyTasksLists;
    int sizeReadyTasksLists0;
    int maxPriorities;
    int maxTaskNameLen;
    long addrDelayedTaskList1;
    long addrDelayedTaskList2;
    long addrPendingReadyList;
    long addrSuspendedTaskList;
    long addrSchedulerRunning;
    
    long offsetTopOfStack;
    long offsetTaskName;
    long offsetPriority;
    long offsetTCBNumber;
    long offsetEventContainer; // xEventListItem.pvContainer
    
    long offsetListNext;
    long offsetListEnd;
    long offsetListItemNext;
    long offsetListItemOwner;
    
    HashMap<Long, ThreadInfo> threads;
    long currentThreadId;
    long selectThreadId;
    
    ThreadContextType threadContextType;
    ThreadContext  threadContext;
    TargetRegisters targetRegisters;
    
    public FreeRTOS(Debugger debugger, OSNode node)
    {
        super(debugger, node);
    }
    
    public boolean detect()
    {
        Long result = this.debugger.evaluateExpression("&pxCurrentTCB");
        if(result == null)
        {
            return false;
        }
        this.addrCurrentTCB = result;
        
        result = this.debugger.evaluateExpression("&pxReadyTasksLists[0]");
        if(result == null)
        {
            return false;
        }
        this.addrReadyTasksLists = result;
        
        result = this.debugger.evaluateExpression("sizeof(pxReadyTasksLists)");
        if(result == null)
        {
            return false;
        }
        this.sizeReadyTasksLists = result.intValue();
        
        result = this.debugger.evaluateExpression("sizeof(pxReadyTasksLists[0])");
        if(result == null)
        {
            return false;
        }
        this.sizeReadyTasksLists0 = result.intValue();
        
        this.maxPriorities = this.sizeReadyTasksLists / this.sizeReadyTasksLists0;
        this.debug(2, "configMAX_PRIORITIES = " + Long.toString(this.maxPriorities));
        
        result = this.debugger.evaluateExpression("&xDelayedTaskList1");
        if(result == null)
        {
            return false;
        }
        this.addrDelayedTaskList1 = result;
        
        result = this.debugger.evaluateExpression("&xDelayedTaskList2");
        if(result == null)
        {
            return false;
        }
        this.addrDelayedTaskList2 = result;
        
        result = this.debugger.evaluateExpression("&xPendingReadyList");
        if(result == null)
        {
            return false;
        }
        this.addrPendingReadyList = result;
        
        result = this.debugger.evaluateExpression("&xSchedulerRunning");
        if(result == null)
        {
            return false;
        }
        this.addrSchedulerRunning = result;
        
        // xSuspendedTaskList is configurable
        result = this.debugger.evaluateExpression("&xSuspendedTaskList");
        if(result == null)
        {
            this.addrSuspendedTaskList = -1;
        }
        else
        {
            this.addrSuspendedTaskList = result;
        }
        
        result = this.debugger.evaluateExpression("&(((TCB_t*)0)->pxTopOfStack)");
        if(result == null)
        {
            return false;
        }
        this.offsetTopOfStack = result;
        
        result = this.debugger.evaluateExpression("&(((TCB_t*)0)->pcTaskName)");
        if(result == null)
        {
            return false;
        }
        this.offsetTaskName = result;
        
        result = this.debugger.evaluateExpression("sizeof(((TCB_t*)0)->pcTaskName)");
        if(result == null)
        {
            return false;
        }
        this.maxTaskNameLen = result.intValue();
        
        result = this.debugger.evaluateExpression("&(((TCB_t*)0)->uxPriority)");
        if(result == null)
        {
            return false;
        }
        this.offsetPriority = result;
        
        // uxTCBNumber is configurable
        result = this.debugger.evaluateExpression("&(((TCB_t*)0)->uxTCBNumber)");
        if(result == null)
        {
            this.offsetTCBNumber = -1;
        }
        else
        {
            this.offsetTCBNumber = result;
        }
        
        result = this.debugger.evaluateExpression("&(((TCB_t*)0)->xEventListItem.pvContainer)");
        if(result == null)
        {
            return false;
        }
        this.offsetEventContainer = result;
        
        result = this.debugger.evaluateExpression("&(((List_t*)0)->xListEnd.pxNext)");
        if(result == null)
        {
            return false;
        }
        this.offsetListNext = result;
        
        result = this.debugger.evaluateExpression("&(((List_t*)0)->xListEnd)");
        if(result == null)
        {
            return false;
        }
        this.offsetListEnd = result;
        
        result = this.debugger.evaluateExpression("&(((ListItem_t*)0)->pxNext)");
        if(result == null)
        {
            return false;
        }
        this.offsetListItemNext = result;
        
        result = this.debugger.evaluateExpression("&(((ListItem_t*)0)->pvOwner)");
        if(result == null)
        {
            return false;
        }
        this.offsetListItemOwner = result;
        
        result = this.debugger.evaluateExpression("&PendSV_Handler");
        if(result != null)
        {
            this.threadContextType = ThreadContextType.CORTEXM;
            this.debug(1, "Cortex-M architecture detected");
        }
        
        if(this.threadContextType == ThreadContextType.CORTEXM)
        {
            this.threadContext = new ThreadContextCortexM(this.threadContextType);
            this.targetRegisters = new TargetRegistersCortexM();
        }
        
        return this.threadContextType != null;
    }
    
    void getThreadList(long addrTaskList, ThreadState state) throws IOException
    {
        long head = this.node.readInt(addrTaskList + this.offsetListNext);
        long next = head;
        
        // list->xListEnd->pxNext != &list->xListEnd
        while(next != addrTaskList + this.offsetListEnd)
        {
            ThreadInfo thread = new ThreadInfo();
            
            long owner = this.node.readInt(next + this.offsetListItemOwner);
            thread.name = this.node.readString(owner + this.offsetTaskName, this.maxTaskNameLen);
            thread.stackPointer = this.node.readInt(owner + this.offsetTopOfStack);
            thread.priority = this.node.readInt(owner + this.offsetPriority);
            if(this.offsetTCBNumber == -1)
            {
                thread.id = owner;
            }
            else
            {
                thread.id = this.node.readInt(owner + this.offsetTCBNumber);
            }
            
            if(thread.id == this.currentThreadId)
            {
                thread.state = ThreadState.Running;
            }
            else
            {
                thread.state = state;
            }
            
            if(thread.state == ThreadState.Suspended)
            {
                long container = this.node.readInt(owner + this.offsetEventContainer);
                if(container != 0)
                {
                    thread.state = ThreadState.Blocked;
                }
            }
            
            this.threads.put(thread.id, thread);
            
            next = this.node.readInt(next + this.offsetListItemNext);
        }
    }
    
    void doUpdateThreadList() throws IOException
    {
        this.threads = new HashMap<Long, ThreadInfo>();
        
        long cur = this.node.readInt(this.addrCurrentTCB);
        if(this.offsetTCBNumber == -1)
        {
            this.currentThreadId = cur;
        }
        else
        {
            this.currentThreadId = this.node.readInt(cur + this.offsetTCBNumber);
        }
        
        for(int i = 0; i < this.maxPriorities; i++)
        {
            long addr = this.addrReadyTasksLists + i * this.sizeReadyTasksLists0;
            this.getThreadList(addr, ThreadState.Ready);
        }
        
        this.getThreadList(this.addrDelayedTaskList1, ThreadState.Blocked);
        this.getThreadList(this.addrDelayedTaskList2, ThreadState.Blocked);
        this.getThreadList(this.addrPendingReadyList, ThreadState.Ready);
        
        if(this.addrSuspendedTaskList != -1)
        {
            this.getThreadList(this.addrSuspendedTaskList, ThreadState.Suspended);
        }
    }
    
    void updateThreadList() throws IOException
    {
        // 仅在执行过目标代码后才需要更新任务列表，引起目标代码执行的命令会将needUpdate设置为真
        if(this.needUpdate)
        {
            // 只有在开启调度器之后，获取任务列表才是有意义的
            if(this.node.readInt(this.addrSchedulerRunning) == 1)
            {
                this.doUpdateThreadList();
            }
        }
        
        this.needUpdate = false;
    }
    
    public String getThreadList(boolean first) throws IOException
    {
        this.updateThreadList();
        
        String threadList = "m";
        if(first)
        {
            if(this.threads != null && !this.threads.isEmpty())
            {
                for(Long id: this.threads.keySet())
                {
                    threadList += Long.toHexString(id) + ",";
                }
                threadList = threadList.substring(0, threadList.length() - 1);
            }
        }
        else
        {
            threadList = "l";
        }
        
        return threadList;
    }
    
    public String getThreadInfo(int id) throws IOException
    {
        this.updateThreadList();
        
        if(this.threads != null && this.threads.containsKey(new Long(id)))
        {
            ThreadInfo thread = this.threads.get(new Long(id));
             String threadInfo = String.format("state:%s;prio:%d;name:\"%s\"", 
                        thread.state.name(), thread.priority, thread.name);
            
            return threadInfo;
        }
        else
        {
            return null;
        }
    }
    
    public boolean setCurrentThread(String op, int id) throws IOException
    {
        this.selectThreadId = id;
        
        if(id == FreeRTOS.THREAD_ID_ANY || id == FreeRTOS.THREAD_ID_ALL)
        {
            return true;
        }
        else
        {
            this.updateThreadList();
            
            if(this.threads != null && this.threads.containsKey(new Long(id)))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
    }
    
    public int getCurrentThread() throws IOException
    {
        this.updateThreadList();
        
        if(this.threads != null)
        {
            return (int)this.currentThreadId;
        }
        else
        {
            return 0;
        }
    }
    
    public void targetRunning()
    {
        super.targetRunning();
        this.selectThreadId = FreeRTOS.THREAD_ID_ANY;
    }
    
    public boolean isAliveThread(int id) throws IOException
    {
        this.updateThreadList();
        
        if(this.threads != null && this.threads.containsKey(new Long(id)))
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    
    String readThreadRegisters() throws IOException
    {
        long id = this.selectThreadId;
        
        if(this.threads != null && this.threads.containsKey(id))
        {
            ThreadInfo thread = this.threads.get(id);
            long stack = thread.stackPointer;
            stack += this.threadContext.offset;
            String registers = this.node.readRegisters();
            String context = this.node.readMemory(stack, this.threadContext.length);
            for(Register reg : this.targetRegisters.arrayRegisters)
            {
                Register threadReg = this.threadContext.getRegister(reg.name);
                threadReg = this.threadContext.getRegister(reg.name);
                
                if(threadReg != null)
                {
                    int offset = threadReg.offset;
                    registers = registers.substring(0, reg.offset * 2) + 
                            context.substring(offset * 2, offset * 2 + reg.length * 2) +
                            registers.substring(reg.offset * 2 + reg.length * 2);
                }
            }
            
            return registers;
        }
        else
        {
            return null;
        }
    }
    
    public String readRegisters() throws IOException
    {
        this.debug(3, "this.selectThreadId=" + this.selectThreadId);
        
        if(this.selectThreadId == FreeRTOS.THREAD_ID_ANY || 
                this.selectThreadId == FreeRTOS.THREAD_ID_ALL)
        {
            return null;
        }
        
        this.updateThreadList();
        
        if(this.selectThreadId == this.currentThreadId)
        {
            return null;
        }
        
        if(this.targetRegisters != null && this.threadContext != null)
        {
            return this.readThreadRegisters();
        }
        else
        {
            return null;
        }
    }
    
    class ThreadContextCortexM extends ThreadContext
    {
        ThreadContextCortexM(ThreadContextType threadContextType)
        {
            super(threadContextType);
            
            int numRegisters = 0;
            if(this.threadContextType == ThreadContextType.CORTEXM)
            {
                this.offset = 0;
                numRegisters = 16;
            }
            else
            {
                assert false;
            }
            
            this.length = numRegisters * 4;
            this.arrayRegisters = new Register[numRegisters];
            
            this.arrayRegisters[0] = new Register("r4", 0, 4);
            this.arrayRegisters[1] = new Register("r5", 4, 4);
            this.arrayRegisters[2] = new Register("r6", 8, 4);
            this.arrayRegisters[3] = new Register("r7", 12, 4);
            
            this.arrayRegisters[4] = new Register("r8", 16, 4);
            this.arrayRegisters[5] = new Register("r9", 20, 4);
            this.arrayRegisters[6] = new Register("r10", 24, 4);
            this.arrayRegisters[7] = new Register("r11", 28, 4);
            
            this.arrayRegisters[8] = new Register("r0", 32, 4);
            this.arrayRegisters[9] = new Register("r1", 36, 4); 
            this.arrayRegisters[10] = new Register("r2", 40, 4);
            this.arrayRegisters[11] = new Register("r3", 44, 4);
            
            this.arrayRegisters[12] = new Register("r12", 48, 4);
            this.arrayRegisters[13] = new Register("lr", 52, 4);
            this.arrayRegisters[14] = new Register("pc", 56, 4);
            this.arrayRegisters[15] = new Register("xpsr", 60, 4);
        }
    }
    
    class ThreadInfo
    {
        long stackPointer;
        ThreadState state;
        long id;
        String name;
        long priority;
    }
    
    enum ThreadState
    {
        Running,
        Ready,
        Blocked,
        Suspended,
    }
}
